#include "stdafx.h"
#include <stdlib.h>
#include "win_service.h"

_SSL_BEGIN
_SSWINOSL_BEGIN

bool svr_is_installed(ss_in_ const tchar *service_name, ss_out_ bool &installed)
{
	bool ret = false;
	SC_HANDLE s_m_h = NULL;
	SC_HANDLE s_h = NULL;

	installed = false;

	s_m_h = ::OpenSCManager(NULL, NULL, SC_MANAGER_QUERY_LOCK_STATUS);
	if(s_m_h == NULL)
		goto finish;

	s_h = ::OpenService(s_m_h, service_name, SERVICE_QUERY_CONFIG);
	installed = (s_h != NULL);

	ret = true;

finish:

	if(s_h != NULL)
	{
		::CloseServiceHandle(s_h);
		s_h = NULL;
	}
	if(s_m_h != NULL)
	{
		::CloseServiceHandle(s_m_h);
		s_m_h = NULL;
	}

	return ret;
}

bool svr_install(const tchar *service_name, const tchar *bin_path, const tchar *display_name,
	DWORD service_type /* = SERVICE_WIN32_OWN_PROCESS */, DWORD service_start_type /* = SERVICE_AUTO_START */,
	const tchar *dependencies /* = _T */, const tchar *start_name /*= NULL*/, const tchar *start_pwd /*= NULL*/)
{
	bool ret = false;
	SC_HANDLE s_m_h = NULL;
	SC_HANDLE s_h = NULL;
	tchar absolute_path[MAX_PATH] = {0};

	if(service_name == NULL)
		goto finish;

	if(bin_path == NULL || _tcslen(bin_path) == 0)
	{
		::GetModuleFileName(NULL, absolute_path, SS_DIMOF(absolute_path));
		bin_path = absolute_path;
	}

	if(display_name == NULL)
		display_name = _T("");

	s_m_h = ::OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
	if(s_m_h == NULL)
		goto finish;

	s_h = ::CreateService(s_m_h, service_name, display_name, SERVICE_ALL_ACCESS, service_type,
		service_start_type, SERVICE_ERROR_NORMAL, bin_path, NULL, NULL, dependencies,
		start_name, start_pwd);
	if(s_h == NULL)
		goto finish;

	ret = true;

finish:

	if(s_h != NULL)
	{
		::CloseServiceHandle(s_h);
		s_h = NULL;
	}
	if(s_m_h != NULL)
	{
		::CloseServiceHandle(s_m_h);
		s_m_h = NULL;
	}

	return ret;
}

bool svr_uninstall(const tchar *service_name)
{
	bool ret = false;
	SC_HANDLE s_m_h = NULL;
	SC_HANDLE s_h = NULL;

	s_m_h = ::OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
	if(s_m_h == NULL)
		goto finish;

	s_h = ::OpenService(s_m_h, service_name, SERVICE_STOP| DELETE);
	if(s_h == NULL)
		goto finish;

	SERVICE_STATUS svr_status;
	::ControlService(s_h, SERVICE_CONTROL_STOP, &svr_status);
	if(svr_status.dwCurrentState != SERVICE_STOPPED)
	{
		while(::QueryServiceStatus(s_h, &svr_status))
		{
			if(svr_status.dwCurrentState == SERVICE_STOP_PENDING)
			{
				Sleep(1000);
			}else
			{
				break;
			}
		}
	}

	if(svr_status.dwCurrentState != SERVICE_STOPPED)
		goto finish;

	if(!::DeleteService(s_h))
		goto finish;

	ret = true;

finish:

	if(s_h != NULL)
	{
		::CloseServiceHandle(s_h);
		s_h = NULL;
	}
	if(s_m_h != NULL)
	{
		::CloseServiceHandle(s_m_h);
		s_m_h = NULL;
	}

	return ret;
}

service_handle svr_ctl_open(const tchar *service_name, DWORD sc_manager_access /*= SC_MANAGER_ALL_ACCESS*/,
	DWORD sc_access /*= SERVICE_ALL_ACCESS*/)
{
	bool ret = false;
	SC_HANDLE s_m_h = NULL;
	SC_HANDLE s_h = NULL;

	s_m_h = ::OpenSCManager(NULL, NULL, sc_manager_access);
	if(s_m_h == NULL)
		goto finish;

	s_h = ::OpenService(s_m_h, service_name, sc_access);
	if(s_h == NULL)
		goto finish;

	ret = true;

finish:

	if(s_h != NULL && !ret)
	{
		::CloseServiceHandle(s_h);
		s_h = NULL;
	}
	if(s_m_h != NULL)
	{
		::CloseServiceHandle(s_m_h);
		s_m_h = NULL;
	}

	return s_h;
}

bool svr_ctl_start(service_handle h, DWORD argc, const tchar **argv)
{
	SC_HANDLE s_h = static_cast<SC_HANDLE>(h);
	if(s_h == NULL)
		return false;

	SERVICE_STATUS svr_status = {0};
	if(!::QueryServiceStatus(s_h, &svr_status))
		return false;

	if(svr_status.dwCurrentState != SERVICE_STOPPED)
		return false;

	if(!::StartService(s_h, argc, argv))
		return false;

	if(!::QueryServiceStatus(s_h, &svr_status))
		return false;

	if(svr_status.dwCurrentState == SERVICE_RUNNING)
		return true;

	while(::QueryServiceStatus(s_h, &svr_status))
	{
		if(svr_status.dwCurrentState == SERVICE_START_PENDING)
		{
			Sleep(1000);
		}else
		{
			break;
		}
	}

	return svr_status.dwCurrentState == SERVICE_RUNNING;
}

bool svr_ctl_query_status(ss_in_ service_handle h, ss_out_ SERVICE_STATUS &status)
{
	SC_HANDLE s_h = static_cast<SC_HANDLE>(h);
	if(s_h == NULL)
		return false;
	return ::QueryServiceStatus(s_h, &status) ? true : false;
}

bool svr_ctl_control(service_handle h, DWORD control_code)
{
	SC_HANDLE s_h = static_cast<SC_HANDLE>(h);
	if(s_h == NULL)
		return false;

	SERVICE_STATUS status;
	return ::ControlService(s_h, control_code, &status) ? true : false;
}

bool svr_ctl_stop(service_handle h)
{
	SC_HANDLE s_h = static_cast<SC_HANDLE>(h);
	if(s_h == NULL)
		return false;

	SERVICE_STATUS svr_status;
	if(!::QueryServiceStatus(s_h, &svr_status))
		return false;

	if(svr_status.dwCurrentState == SERVICE_STOPPED)
		return true;

	if(!::ControlService(s_h, SERVICE_CONTROL_STOP, &svr_status))
		return false;

	if(svr_status.dwCurrentState == SERVICE_STOPPED)
		return true;

	while(::QueryServiceStatus(s_h, &svr_status))
	{
		if(svr_status.dwCurrentState == SERVICE_START_PENDING)
		{
			Sleep(1000);
		}else
		{
			break;
		}
	}

	return (svr_status.dwCurrentState == SERVICE_STOPPED);
}

tchar global_service_host_name[] = _T("service1");
service_host_interface *global_service_host = NULL;
DWORD global_service_host_thread_id = 0;
SERVICE_STATUS global_service_status = {0};
SERVICE_STATUS_HANDLE global_service_status_handle = NULL;
bool s_report_service_host_status(DWORD current_state)
{
	global_service_status.dwCurrentState = current_state;
	return ::SetServiceStatus(global_service_status_handle, &global_service_status) ? true : false;
}

DWORD s_get_service_host_status()
{
	return global_service_status.dwCurrentState;
}

void WINAPI s_default_service_control(DWORD ctl_code)
{
	switch(ctl_code)
	{
	case SERVICE_CONTROL_STOP:
		s_report_service_host_status(SERVICE_STOP_PENDING);
		break;
	case SERVICE_CONTROL_PAUSE:
		s_report_service_host_status(SERVICE_PAUSE_PENDING);
		break;
	case SERVICE_CONTROL_CONTINUE:
		s_report_service_host_status(SERVICE_CONTINUE_PENDING);
		break;
	default:
		break;
	}

	global_service_host->on_control(ctl_code);

	switch(ctl_code)
	{
	case SERVICE_CONTROL_STOP:
		// can't report stopped, after reported, the service MAY be terminated soon.
		// so, we can report stopped in main function
		//ReportServiceStatus(SERVICE_STOPPED);
		break;
	case SERVICE_CONTROL_PAUSE:
		s_report_service_host_status(SERVICE_PAUSED);
		break;
	case SERVICE_CONTROL_CONTINUE:
		s_report_service_host_status(SERVICE_RUNNING);
		break;
	case SERVICE_CONTROL_INTERROGATE:
		s_report_service_host_status(s_get_service_host_status());
		break;
	case SERVICE_CONTROL_SHUTDOWN:
		s_report_service_host_status(SERVICE_STOPPED);
		break;
	default:
		break;
	}
}

VOID WINAPI s_service_main()
{
	global_service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	global_service_status.dwCurrentState = SERVICE_STOPPED;
	global_service_status.dwControlsAccepted = global_service_host->get_control_accepte_code();
	global_service_status.dwWin32ExitCode = 0;
	global_service_status.dwServiceSpecificExitCode = 0;
	global_service_status.dwCheckPoint = 0;
	global_service_status.dwWaitHint = 0;

	global_service_status_handle = ::RegisterServiceCtrlHandler(global_service_host_name,
		s_default_service_control);
	if(global_service_status_handle == NULL)
		return;

	s_report_service_host_status(SERVICE_START_PENDING);
	global_service_status.dwWin32ExitCode = S_OK;
	s_report_service_host_status(SERVICE_RUNNING);

	SS_ASSERT(global_service_host != NULL);

	global_service_host->main_proc();

	s_report_service_host_status(SERVICE_STOPPED);

	// wait for the service is terminated
	Sleep(10000);

	// the code should not run to there
	SS_ASSERT_HERE;
}
bool svr_host_start(service_host_interface *host)
{
	if(host == NULL)
		return false;

	SERVICE_TABLE_ENTRY entries[] =
	{
		{ global_service_host_name, (LPSERVICE_MAIN_FUNCTION)s_service_main },
		{ NULL, NULL },
	};

	global_service_host = host;
	global_service_host_thread_id = ::GetCurrentThreadId();
	if(!::StartServiceCtrlDispatcher(entries))
	{
		if(::GetLastError() != ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
			return false;
		host->main_proc();
	}

	return true;
}

struct svr_host_interactive_call_struct
{
	HWINSTA win_sta;
	HDESK desk;
	HWINSTA current_win_sta;
	HDESK current_desk;

	svr_host_interactive_call_struct()
	{
		win_sta = NULL;
		desk = NULL;
		current_win_sta = NULL;
		current_desk = NULL;
	}

	~svr_host_interactive_call_struct()
	{
		if(win_sta != NULL)
		{
			if(current_win_sta != NULL)
			{
				::SetProcessWindowStation(current_win_sta);
			}
			::CloseWindowStation(win_sta);
			win_sta = NULL;
		}
		current_win_sta = NULL;

		if(desk != NULL)
		{
			if(current_desk != NULL)
			{
				::SetThreadDesktop(current_desk);
			}
			::CloseDesktop(desk);
			desk = NULL;
		}
		current_desk = NULL;
	}
};

HANDLE svr_host_before_interactive_call()
{
	svr_host_interactive_call_struct *interactive_struct = new svr_host_interactive_call_struct;
	if(interactive_struct == NULL)
		return NULL;

	bool ret = false;
	interactive_struct->current_win_sta = ::GetProcessWindowStation();
	if(interactive_struct->current_win_sta == NULL)
		goto finish;

	interactive_struct->current_desk = ::GetThreadDesktop(::GetCurrentThreadId());
	if(interactive_struct->current_desk == NULL)
		goto finish;

	/*
	interactive_struct->win_sta = ::OpenWindowStation(_T("WinSta0"), FALSE,
		WINSTA_ACCESSCLIPBOARD|WINSTA_ACCESSGLOBALATOMS||WINSTA_CREATEDESKTOP|
		WINSTA_ENUMDESKTOPS|WINSTA_ENUMERATE|WINSTA_EXITWINDOWS|WINSTA_READATTRIBUTES|
		WINSTA_READSCREEN|WINSTA_WRITEATTRIBUTES);
	*/
	interactive_struct->win_sta = ::OpenWindowStation(_T("WinSta0"), FALSE,
		WINSTA_ALL_ACCESS);
	if(interactive_struct->win_sta == NULL)
		goto finish;

	if(!::SetProcessWindowStation(interactive_struct->win_sta))
		goto finish;

	interactive_struct->desk = ::OpenDesktop(TEXT("Default"), 0, FALSE,
		DESKTOP_CREATEMENU|DESKTOP_CREATEWINDOW|DESKTOP_ENUMERATE|
		DESKTOP_HOOKCONTROL|DESKTOP_JOURNALPLAYBACK|DESKTOP_JOURNALRECORD|
		DESKTOP_READOBJECTS|DESKTOP_SWITCHDESKTOP| DESKTOP_WRITEOBJECTS);
	if(interactive_struct->desk == NULL)
		goto finish;

	if(!::SetThreadDesktop(interactive_struct->desk))
		goto finish;

	ret = true;
finish:
	if(!ret)
	{
		delete interactive_struct;
		interactive_struct = NULL;
	}
	return interactive_struct;
}

bool svr_host_after_interactive_call(HANDLE h)
{
	svr_host_interactive_call_struct *interactive_struct = static_cast<svr_host_interactive_call_struct*>(h);
	if(interactive_struct == NULL)
		return false;
	delete interactive_struct;
	interactive_struct = NULL;
	return true;
}

_SSWINOSL_END
_SSL_END
